import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // home: ScrollControllerTestRoute(),
      home: const MyHomePage(
        title: 'demo',
      ),
    );
  }
}
// Animation是一个抽象类，它本身和UI渲染没有任何关系，而它主要的功能是保存动画的插值和状态；其中一个比较常用的Animation类是Animation<double>。Animation对象是一个在一段时间内依次生成一个区间(Tween)之间值的类。Animation对象在整个动画执行过程中输出的值可以是线性的、曲线的、一个步进函数或者任何其他曲线函数等等，这由Curve来决定。 根据Animation对象的控制方式，动画可以正向运行（从起始状态开始，到终止状态结束），也可以反向运行，甚至可以在中间切换方向。Animation还可以生成除double之外的其他类型值，如：Animation<Color> 或Animation<Size>。在动画的每一帧中，我们可以通过Animation对象的value属性获取动画的当前状态值

// 我们可以通过Animation来监听动画每一帧以及执行状态的变化，Animation有如下两个方法：

// addListener()；它可以用于给Animation添加帧监听器，在每一帧都会被调用。帧监听器中最常见的行为是改变状态后调用setState()来触发UI重建。
// addStatusListener()；它可以给Animation添加“动画状态改变”监听器；动画开始、结束、正向或反向（见AnimationStatus定义）时会调用状态改变的监听器。

// Curve 动画曲线
// curve: Curves.easeIn
// 当然我们也可以创建自己Curve，例如我们定义一个正弦曲线：

// class ShakeCurve extends Curve {
//   @override
//   double transform(double t) {
//     return math.sin(t * math.PI * 2);
//   }
// }

// AnimationController用于控制动画，它包含动画的启动forward()、停止stop() 、反向播放 reverse()等方法。AnimationController会在动画的每一帧，就会生成一个新的值。默认情况下，AnimationController在给定的时间段内线性的生成从 0.0 到1.0（默认区间）的数字。 例如，下面代码创建一个Animation对象（但不会启动动画）：
// AnimationController生成数字的区间可以通过lowerBound和upperBound来指定，如：
// final AnimationController controller = AnimationController(
//  duration: const Duration(milliseconds: 2000),
//  lowerBound: 10.0,
//  upperBound: 20.0,
//  vsync: this
// );
// Flutter 应用在启动时都会绑定一个SchedulerBinding，通过SchedulerBinding可以给每一次屏幕刷新添加回调，而Ticker就是通过SchedulerBinding来添加屏幕刷新回调，这样一来，每次屏幕刷新都会调用TickerCallback。使用Ticker(而不是Timer)来驱动动画会防止屏幕外动画（动画的UI不在当前屏幕时，如锁屏时）消耗不必要的资源，因为Flutter中屏幕刷新时会通知到绑定的SchedulerBinding，而Ticker是受SchedulerBinding驱动的，由于锁屏后屏幕会停止刷新，所以Ticker就不会再触发。

// 通常我们会将SingleTickerProviderStateMixin添加到State的定义中，然后将State对象作为vsync的值

// Tween
// 默认情况下，AnimationController对象值的范围是[0.0，1.0]。如果我们需要构建UI的动画值在不同的范围或不同的数据类型，则可以使用Tween来添加映射以生成不同的范围或数据类型的值。例如，像下面示例，Tween生成[-200.0，0.0]的值：
// final Tween doubleTween = Tween<double>(begin: -200.0, end: 0.0);
// Tween构造函数需要begin和end两个参数。Tween的唯一职责就是定义从输入范围到输出范围的映射
// ColorTween将动画输入范围映射为两种颜色值之间过渡输出
// final Tween colorTween =  ColorTween(begin: Colors.transparent, end: Colors.black54);

// 要使用 Tween 对象，需要调用其animate()方法，然后传入一个控制器对象。例如，以下代码在 500 毫秒内生成从 0 到 255 的整数值。
// final AnimationController controller = AnimationController(
//   duration: const Duration(milliseconds: 500),
//   vsync: this,
// );
// Animation<int> alpha = IntTween(begin: 0, end: 255).animate(controller);

// 以下示例构建了一个控制器、一条曲线和一个 Tween：
// final AnimationController controller = AnimationController(
//   duration: const Duration(milliseconds: 500),
//   vsync: this,
// );
// final Animation curve = CurvedAnimation(parent: controller, curve: Curves.easeOut);
// Animation<int> alpha = IntTween(begin: 0, end: 255).animate(curve);

// 动画的原理其实就是每一帧绘制不同的内容，一般都是指定起始和结束状态，然后在一段时间内从起始状态逐渐变为结束状态，而具体某一帧的状态值会根据动画的进度来算出，因此，Flutter 中给有可能会做动画的一些状态属性都定义了静态的 lerp 方法（线性插值），比如：

//a 为起始颜色，b为终止颜色，t为当前动画的进度[0,1]
// Color.lerp(a, b, t);
// lerp 的计算一般遵循： 返回值 = a + (b - a) * t，其它拥有 lerp 方法的类
// Size.lerp(a, b, t)
// Rect.lerp(a, b, t)
// Offset.lerp(a, b, t)
// Decoration.lerp(a, b, t)
// Tween.lerp(t) //起始状态和终止状态在构建 Tween 的时候已经指定了

// 需要注意，lerp 是线性插值，意思是返回值和动画进度t是成一次函数（y = kx + b）关系，因为一次函数的图像是一条直线，所以叫线性插值。如果我们想让动画按照一个曲线来执行，我们可以对 t 进行映射，比如要实现匀加速效果，则 t' = at²+bt+c，然后指定加速度 a 和 b 即可（大多数情况下需保证 t' 的取值范围在[0,1]，当然也有一些情况可能会超出该取值范围，比如弹簧（bounce）效果），而不同 Curve 可以按照不同曲线执行动画的的原理本质上就是对 t 按照不同映射公式进行映射
class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      // body: Test1(),
    );
  }
}
